﻿using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
/************************************************************************/
/* Author:huliang
 * Email:huliang@yahoo.cn
 * QQ:12658501
 * 说明：转载请注明出处
/************************************************************************/

namespace BuyTicket
{
    public enum HttpState
    {
        HTTP_200,
        HTTP_302,
        HTTP_404,
        HTTP_UnKnow
    }

    public class RequestArgs
    {
        public RequestArgs()
        {
            this.Accept = "*/*";
            this.IsPhone = false;
        }

        public RequestArgs(string url, string host)
        {
            this.Url = url;
            this.Host = host;
            this.Accept = "*/*";
            this.IsPhone = false;
        }

        public string Url 
        {
            get; 
            set; 
        }

        public string Host 
        { 
            get; 
            set; 
        }

        public string Accept 
        { 
            get; 
            set;
        }

        public string Referer 
        { 
            get; 
            set; 
        }

        public string Cookie 
        { 
            get; 
            set; 
        }

        public string Body 
        {
            get; 
            set; 
        }

        public bool IsPhone
        {
            get;
            set;
        }
    }

    public class HttpResponse
    {
        internal HttpResponse(string header,
            byte[] body)
        {
            this.Header = header;
            this.Body = body;
        }

        public string  Header
        {
            get;
            private set;
        }

        public byte[] Body
        {
            get;
            private set;
        }
    }

    public static class HttpHelper
    {
        /// <summary>
        /// 提交方法
        /// </summary>
        enum HttpMethod
        {
            GET,
            POST
        }

        #region HttpWebRequest & HttpWebResponse

        /// <summary>
        /// Get方法
        /// </summary>
        /// <param name="geturl">请求地址</param>
        /// <param name="cookieser">Cookies存储器</param>
        /// <returns>请求返回的Stream</returns>
        public static string Get(string url,
            CookieContainer cookies,
            Encoding encoding)
        {
            return InternalHttp(HttpMethod.GET, url, null, cookies, encoding);
        }

        public static Stream Get(string url,
            CookieContainer cookies)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "GET";
            request.UserAgent = "Mozilla/5.0 (Windows NT 6.1;MSIE 6.0;)";
            request.CookieContainer = cookies;
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            return response.GetResponseStream();
        }

        /// <summary>
        /// Post方法
        /// </summary>
        /// <param name="posturl">请求地址</param>
        /// <param name="bytes">Post数据</param>
        /// <param name="cookieser">Cllkies存储器</param>
        /// <returns>请求返回的流</returns>
        public static string Post(string url,
            byte[] bytes,
            CookieContainer cookies,
            Encoding encoding)
        {
            return InternalHttp(HttpMethod.POST, url, bytes, cookies, encoding);
        }

        /// <summary>
        /// Http操作
        /// </summary>
        /// <param name="method">请求方式</param>
        /// <param name="url">请求地址</param>
        /// <param name="bytes">提交数据</param>
        /// <param name="cookieser">Cookies存储器</param>
        /// <returns>请求结果</returns>
        static string InternalHttp(HttpMethod method,
            string url,
            byte[] bytes,
            CookieContainer cookies,
            Encoding encoding)
        {
            if (string.IsNullOrEmpty(url))
                throw new ArgumentNullException("访问url不能为空");
            if (method == HttpMethod.POST)
            {
                if (bytes == null)
                    throw new ArgumentNullException("提交的post数据不能为空");
            }
            if (cookies == null)
                throw new ArgumentNullException("Cookies存储器不能为空");
            try
            {
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                request.AllowAutoRedirect = true;
                request.Method = method.ToString();
                request.UserAgent = "Mozilla/5.0 (Windows NT 6.1;MSIE 9.0;)";
                request.CookieContainer = cookies;
                if (method == HttpMethod.POST)
                {
                    request.ContentType = "application/x-www-form-urlencoded";
                    request.ContentLength = bytes.Length;
                    using (Stream stream = request.GetRequestStream())
                    {
                        stream.Write(bytes, 0, bytes.Length);
                        stream.Flush();
                        stream.Close();
                    }
                }
                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    using (StreamReader reader = new StreamReader(response.GetResponseStream(), encoding))
                    {
                        return reader.ReadToEnd();
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            return null;
        }

        #endregion

        #region Socket

        static bool ValidateServerCertificate(
                 object sender,
                 X509Certificate certificate,
                 X509Chain chain,
                 SslPolicyErrors sslPolicyErrors)
        {
            /*
            if (sslPolicyErrors == SslPolicyErrors.None)
                return true;
            Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
            return false;
            */
            return true;
        }

        public static HttpResponse Get(IPEndPoint endpoint,
            RequestArgs args)
        {
            return Get(endpoint, args, null);
        }

        public static HttpResponse Get(IPEndPoint endpoint,
            RequestArgs args, 
            X509CertificateCollection certs)
        {
            return InternalSslSocketHttp(HttpMethod.GET,endpoint, args,certs);
        }

        public static HttpResponse Post(IPEndPoint endpoint,
            RequestArgs args)
        {
            return Post(endpoint, args, null);
        }

        public static HttpResponse Post(IPEndPoint endpoint,
            RequestArgs args,
            X509CertificateCollection certs)
        {
            return InternalSslSocketHttp(HttpMethod.POST, endpoint, args, certs);
        }

        static HttpResponse InternalSslSocketHttp(HttpMethod method,
            IPEndPoint endpoint,
            RequestArgs args,
            X509CertificateCollection certs)
        {
            TcpClient tcp = new TcpClient();
            try
            {
                tcp.Connect(endpoint);
                if (tcp.Connected)
                {
                    byte[] buff = ParseHttpArgs(method, args);  //生成协议包
                    if (certs != null)
                    {
                        using (SslStream ssl = new SslStream(tcp.GetStream(),
                                                false,
                                                new RemoteCertificateValidationCallback(ValidateServerCertificate),
                                                null))
                        {
                            ssl.AuthenticateAsClient("ServerName",
                                certs,
                                SslProtocols.Tls,
                                false);
                            if (ssl.IsAuthenticated)
                            {
                                ssl.Write(buff);
                                ssl.Flush();
                                return ParseResponse(ssl);  //解析返回结果
                            }
                        }
                    }
                    else
                    {
                        using (NetworkStream ns = tcp.GetStream())
                        {
                            ns.Write(buff, 0, buff.Length);
                            ns.Flush();
                            return ParseResponse(ns);  //解析返回结果
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            return null;
        }

        private static HttpResponse ParseResponse(Stream sm)
        {
            //尝试10秒时间读取协议头
            CancellationTokenSource source = new CancellationTokenSource();
            Task<string> myTask = Task.Factory.StartNew<string>(
                new Func<object, string>(ReadHeaderProcess),
                sm,
                source.Token);
            if (myTask.Wait(30*1000))   //设置解析HTTP返回头超时时间30秒
            {
                string header = myTask.Result;
                if (!string.IsNullOrEmpty(header))
                {
                    byte[] buff = null;
                    int start = header.ToUpper().IndexOf("CONTENT-LENGTH");
                    int content_length = -1;  //fix bug
                    if (start > 0)
                    {
                        string temp = header.Substring(start, header.Length - start);
                        string[] sArry = Regex.Split(temp, "\r\n");
                        content_length = Convert.ToInt32(sArry[0].Split(':')[1]);
                        if (content_length > 0)
                        {
                            buff = new byte[content_length];
                            int allread = sm.Read(buff, 0, buff.Length);
                            while (allread < content_length)
                            {
                                allread += sm.Read(buff, allread, buff.Length - allread);
                            }
                        }
                    }
                    else
                    {
                        buff = ReadResponse(sm);
                    }
                    return new HttpResponse(header, buff);
                }
            }
            else
            {
                source.Cancel();  //超时的话，别忘记取消任务哦
            }
            return null;
        }

        static string ReadHeaderProcess(object args)
        {
            Stream sm = (Stream)args;
            StringBuilder bulider = new StringBuilder();
            while (true)
            {
                try
                {
                    int read = sm.ReadByte();
                    if (read != -1)
                    {
                        byte b = (byte)read;
                        bulider.Append((char)b);
                    }
                    string temp = bulider.ToString();
                    if (temp.Contains("\r\n\r\n"))
                    {
                        break;
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    return null;
                }
            }
            return bulider.ToString();
        }

        static byte[] ReadResponse(Stream sm)
        {
            DateTime now = DateTime.Now;
            ArrayList array = new ArrayList();
            int length = 0;
            while (true)
            {
                byte[] buff = new byte[1024];
                try
                {
                    int len = sm.Read(buff, 0, buff.Length);
                    if (len > 0)
                    {
                        length += len;
                        byte[] reads = new byte[len];
                        Array.Copy(buff, 0, reads, 0, len);
                        array.Add(reads);
                        if (len < buff.Length)
                        {
                            break; //fix bug
                        }
                    }
                    else
                    {
                        break;  //fix bug
                    }
                }
                catch (Exception)
                {
                    break;
                }
                finally
                {
                    Thread.Sleep(200);
                }
            }
            byte[] bytes = new byte[length];
            int index = 0;
            for (int i = 0; i < array.Count; i++)
            {
                byte[] temp = (byte[])array[i];
                Array.Copy(temp, 0, bytes,
                    index, temp.Length);
                index += temp.Length;
            }
            return bytes;
        }

        #endregion

        #region  Helper

        static byte[] ParseHttpArgs(HttpMethod method, RequestArgs args)
        {
            StringBuilder bulider = new StringBuilder();
            if (method.Equals(HttpMethod.POST))
            {
                bulider.AppendLine(string.Format("POST {0} HTTP/1.1",
                    args.Url));
                bulider.AppendLine("Content-Type: application/x-www-form-urlencoded");
            }
            else
            {
                bulider.AppendLine(string.Format("GET {0} HTTP/1.1",
                args.Url));
            }
            bulider.AppendLine(string.Format("Host: {0}",
                args.Host));
            if (args.IsPhone)
            {
                bulider.AppendLine("User-Agent: Mozilla/5.0 (Linux; U; Android 4.0.1;) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30");
            }
            else
            {
                bulider.AppendLine("User-Agent: Mozilla/5.0 (Windows NT 6.1; IE 9.0)");
            }
            if (!string.IsNullOrEmpty(args.Referer))
                bulider.AppendLine(string.Format("Referer: {0}",
                    args.Referer));
            bulider.AppendLine("Connection: keep-alive");
            bulider.AppendLine(string.Format("Accept: {0}",
                args.Accept));
            if(!string.IsNullOrEmpty(args.Cookie))
                bulider.AppendLine(string.Format("Cookie: {0}",
                    args.Cookie));
            if (method.Equals(HttpMethod.POST))
            {
                bulider.AppendLine(string.Format("Content-Length: {0}\r\n",
                   Encoding.Default.GetBytes(args.Body).Length));
                bulider.Append(args.Body);
            }
            else
            {
                bulider.Append("\r\n");
            }
            string header = bulider.ToString();
            return Encoding.Default.GetBytes(header);
        }

        public static string ParseHost(string url)
        {
            string result = string.Empty;
            try {
                Uri uri = new Uri(url);
                result = uri.Host;
            }
            catch(Exception ex) {
                Console.WriteLine(ex.Message);
            }
            return result;
        }

        public static string GetAddress(string host)
        {
            string result = string.Empty;
            try
            {
                IPAddress[] address = Dns.GetHostAddresses(host);
                if (address.Length > 0)
                {
                    result = address[0].ToString();
                }
            }
            catch { }
            return result;
        }

        public static string ParseCookies(string responseHeader)
        {
            StringBuilder cookies = new StringBuilder();
            using (StringReader reader = new StringReader(responseHeader))
            {
                string strLine = reader.ReadLine();
                while (strLine != null)
                {
                    if (strLine.StartsWith("Set-Cookie:"))
                    {
                        string temp = strLine.Remove(0, 12);
                        if (!temp.EndsWith(";"))
                        {
                            temp = temp + ";";
                        }
                        cookies.Append(temp);
                    }
                    strLine = reader.ReadLine();
                }
            }
            return ParseToString(ParseToHashtable(cookies.ToString()));
        }

        public static string ParseLocation(string responseHeader)
        {
            string result = string.Empty;
            using (StringReader reader = new StringReader(responseHeader))
            {
                string strLine = reader.ReadLine();
                while (strLine != null)
                {
                    if (strLine.StartsWith("Location:"))
                    {
                        result = strLine.Remove(0, 10);
                    }
                    strLine = reader.ReadLine();
                }
            }
            return result;
        }

        public static string MergeCookies(string cookies, string cookies2)
        {

            try
            {
                Dictionary<string, string> hashTable = ParseToHashtable(cookies);
                Dictionary<string, string> hashTable2 = ParseToHashtable(cookies2);
                foreach (var ck in hashTable2)
                {
                    if (hashTable.ContainsKey(ck.Key))
                    {
                        hashTable[ck.Key] = ck.Value;
                    }
                }
               return ParseToString(hashTable);
            }
            catch
            {
                return cookies + cookies2;
            }
        }

        public static Dictionary<string, string> ParseToHashtable(string cookies)
        {
            Dictionary<string, string> result = new Dictionary<string, string>();
            string[] sArry = Regex.Split(cookies, ";");
            foreach (var cookie in sArry)
            {
                if (string.IsNullOrEmpty(cookie) == false)
                {
                    if (cookie.Contains("="))
                    {
                        string[] temp = cookie.Split('=');
                        if (result.ContainsKey(temp[0]) == false)  //过滤Key
                        {
                            result.Add(temp[0],
                                cookie.Remove(0, temp[0].Length + 1) + ";");
                        }
                    }
                    else
                    {
                        if (result.ContainsKey(cookie) == false)  //过滤Key
                        {
                            result.Add(cookie,
                                ";");
                        }
                    }
                    
                }
            }
            return result;
        }

        static string ParseToString(Dictionary<string, string> hashTable)
        {
            StringBuilder bulider = new StringBuilder();
            foreach (var ck in hashTable)
            {
                bulider.Append(string.Format("{0}={1}",
                    ck.Key,
                    ck
                    .Value));
            }
            return bulider.ToString();
        }

        public static string GetAbsolutePath(string url)
        {
            try {
                Uri uri = new Uri(url);
                return uri.AbsolutePath;
            }
            catch
            {
                return url;
            }
        }
        #endregion
    }
}
